Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
80.00% covered (warning)
80.00%
4 / 5
CRAP
98.28% covered (success)
98.28%
57 / 58
EntityWithGrantedValuesPropertySetter
0.00% covered (danger)
0.00%
0 / 1
80.00% covered (warning)
80.00%
4 / 5
29
98.28% covered (success)
98.28%
57 / 58
 __construct
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
5 / 5
 setData
0.00% covered (danger)
0.00%
0 / 1
5
96.30% covered (success)
96.30%
26 / 27
 checkViewableAttributeGroup
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
3 / 3
 checkViewableLocalizableAttribute
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
6 / 6
 checkEditableAttribute
100.00% covered (success)
100.00%
1 / 1
17
100.00% covered (success)
100.00%
17 / 17
<?php
/*
 * This file is part of the Akeneo PIM Enterprise Edition.
 *
 * (c) 2017 Akeneo SAS (http://www.akeneo.com)
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
namespace Akeneo\Pim\Permission\Component\Updater\Setter;
use Akeneo\Pim\Enrichment\Component\Product\Model\EntityWithValuesInterface;
use Akeneo\Pim\Enrichment\Component\Product\Model\ValueInterface;
use Akeneo\Pim\Permission\Component\Attributes;
use Akeneo\Pim\Permission\Component\Exception\ResourceAccessDeniedException;
use Akeneo\Pim\Structure\Component\Model\AttributeInterface;
use Akeneo\Tool\Component\StorageUtils\Exception\InvalidObjectException;
use Akeneo\Tool\Component\StorageUtils\Exception\UnknownPropertyException;
use Akeneo\Tool\Component\StorageUtils\Repository\IdentifiableObjectRepositoryInterface;
use Akeneo\Tool\Component\StorageUtils\Updater\PropertySetterInterface;
use Doctrine\Common\Util\ClassUtils;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
/**
 * Check if the property is granted. A property is granted if:
 *   - it belongs to a granted attribute group
 *   - it's localizable and the locale is granted
 *
 * If property is only "viewable", we accept the submission from the moment it has not been modified.
 *
 * @author Marie Bochu <marie.bochu@akeneo.com>
 */
class EntityWithGrantedValuesPropertySetter implements PropertySetterInterface
{
    /** @var PropertySetterInterface */
    private $propertySetter;
    /** @var AuthorizationCheckerInterface */
    private $authorizationChecker;
    /** @var IdentifiableObjectRepositoryInterface */
    private $attributeRepository;
    /** @var IdentifiableObjectRepositoryInterface */
    private $localeRepository;
    /**
     * @param PropertySetterInterface               $propertySetter
     * @param AuthorizationCheckerInterface         $authorizationChecker
     * @param IdentifiableObjectRepositoryInterface $attributeRepository
     * @param IdentifiableObjectRepositoryInterface $localeRepository
     */
    public function __construct(
        PropertySetterInterface $propertySetter,
        AuthorizationCheckerInterface $authorizationChecker,
        IdentifiableObjectRepositoryInterface $attributeRepository,
        IdentifiableObjectRepositoryInterface $localeRepository
    ) {
        $this->propertySetter = $propertySetter;
        $this->authorizationChecker = $authorizationChecker;
        $this->attributeRepository = $attributeRepository;
        $this->localeRepository = $localeRepository;
    }
    /**
     * {@inheritdoc}
     */
    public function setData($entityWithValues, $field, $data, array $options = [])
    {
        if (!$entityWithValues instanceof EntityWithValuesInterface) {
            throw InvalidObjectException::objectExpected(ClassUtils::getClass($entityWithValues), EntityWithValuesInterface::class);
        }
        $attribute = $this->attributeRepository->findOneByIdentifier($field);
        if (null === $attribute) {
            $this->propertySetter->setData($entityWithValues, $field, $data, $options);
            return;
        }
        $channelCode = $options['scope'];
        $localeCode = $options['locale'];
        $permissions = [
            'view_attribute' => $this->authorizationChecker->isGranted([Attributes::VIEW_ATTRIBUTES], $attribute),
            'edit_attribute' => $this->authorizationChecker->isGranted([Attributes::EDIT_ATTRIBUTES], $attribute),
            'view_locale'    => null,
            'edit_locale'    => null,
        ];
        $this->checkViewableAttributeGroup($attribute, $permissions);
        if (null !== $localeCode) {
            $locale = $this->localeRepository->findOneByIdentifier($localeCode);
            if (null === $locale) {
                throw new UnknownPropertyException($localeCode, sprintf(
                    'Attribute "%s" expects an existing and activated locale, "%s" given.',
                    $attribute->getCode(),
                    $localeCode
                ));
            }
            $permissions = array_merge($permissions, [
                'view_locale' => $this->authorizationChecker->isGranted([Attributes::VIEW_ITEMS], $locale),
                'edit_locale' => $this->authorizationChecker->isGranted([Attributes::EDIT_ITEMS], $locale)
            ]);
            $this->checkViewableLocalizableAttribute($attribute, $permissions, $localeCode);
        }
        $oldValue = $entityWithValues->getValue($field, $localeCode, $channelCode);
        $this->propertySetter->setData($entityWithValues, $field, $data, $options);
        $newValue = $entityWithValues->getValue($field, $localeCode, $channelCode);
        $this->checkEditableAttribute($attribute, $permissions, $oldValue, $newValue);
    }
    /**
     * @param AttributeInterface $attribute
     * @param array              $permissions
     */
    private function checkViewableAttributeGroup(AttributeInterface $attribute, array $permissions): void
    {
        if (!$permissions['view_attribute'] && !$permissions['edit_attribute']) {
            throw UnknownPropertyException::unknownProperty($attribute->getCode());
        }
    }
    /**
     * @param AttributeInterface $attribute
     * @param array              $permissions
     * @param string             $localeCode
     */
    private function checkViewableLocalizableAttribute(
        AttributeInterface $attribute,
        array $permissions,
        string $localeCode
    ): void {
        if (!$permissions['view_locale'] && !$permissions['edit_locale']) {
            throw new UnknownPropertyException($localeCode, sprintf(
                'Attribute "%s" expects an existing and activated locale, "%s" given.',
                $attribute->getCode(),
                $localeCode
            ));
        }
    }
    /**
     * @param AttributeInterface  $attribute
     * @param array               $permissions
     * @param ValueInterface|null $oldValue
     * @param ValueInterface|null $newValue
     */
    private function checkEditableAttribute(
        AttributeInterface $attribute,
        array $permissions,
        ?ValueInterface $oldValue,
        ?ValueInterface $newValue
    ): void {
        $valueIsDeleted = null !== $oldValue && $oldValue->hasData() && null === $newValue;
        $valueIsAdded = null === $oldValue && null !== $newValue && $newValue->hasData();
        $valueIsChanged = null !== $oldValue && null !== $newValue && !$oldValue->isEqual($newValue);
        if (!$valueIsChanged && !$valueIsAdded && !$valueIsDeleted) {
            return;
        }
        if ($permissions['view_attribute'] && !$permissions['edit_attribute']) {
            throw new ResourceAccessDeniedException($newValue, sprintf(
                'Attribute "%s" belongs to the attribute group "%s" on which you only have view permission.',
                $attribute->getCode(),
                $attribute->getGroup()->getCode()
            ));
        }
        if (null !== $newValue->getLocaleCode() &&
            true === $permissions['view_locale'] &&
            false === $permissions['edit_locale']
        ) {
            throw new ResourceAccessDeniedException($newValue, sprintf(
                'You only have a view permission on the locale "%s".',
                $newValue->getLocaleCode()
            ));
        }
    }
}